Skip to content

feat(extensions): add assess idea assessment pipeline extension#3568

Merged
mnriem merged 13 commits into
github:mainfrom
mnriem:mnriem-feat-assess-extension
Jul 17, 2026
Merged

feat(extensions): add assess idea assessment pipeline extension#3568
mnriem merged 13 commits into
github:mainfrom
mnriem:mnriem-feat-assess-extension

Conversation

@mnriem

@mnriem mnriem commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds a new opt-in, role-neutral extensionassess, the "Idea Assessment Pipeline" — covering the discovery/problem-assessment work that happens before spec-driven development begins (i.e., before /speckit.specify).

Today Spec Kit starts at the point where you already know what you're building. assess fills the gap in front of that: a lightweight, structured funnel for pressure-testing an idea before it earns a spec. It is deliberately role-neutral (named for the activity, not a job title) so it composes cleanly into role bundles rather than competing with them.

Pipeline (five stages, one artifact each)

Each command writes a single markdown artifact under .specify/assessments/<slug>/:

Command Stage Artifact
/speckit.assess.intake Capture the raw idea intake.md
/speckit.assess.research Gather evidence research.md
/speckit.assess.define Sharpen the problem problem.md
/speckit.assess.shape Outline a concept concept.md
/speckit.assess.decide Go / needs-clarification / kill decision.md
  • A go verdict hands off to /speckit.specify.
  • Killing an idea is a first-class success outcome, not a failure.
  • It registers no lifecycle hooks — assess is a separate business process and never inserts itself into /speckit.specify. The only coupling is the forward decide → /speckit.specify handoff.

Registration (core opt-in)

  • extensions/catalog.json: bundled entry ("bundled": true), alphabetically before bug.
  • pyproject.toml: force-include maps extensions/assessspecify_cli/core_pack/extensions/assess so it ships in the installed wheel. Verified by building the wheel and confirming all files land in core_pack.
  • Not auto-installed by specify init; users opt in with specify extension add assess.

It mirrors the existing bug extension's conventions (slug resolution, URL Trust Policy, __SPECKIT_COMMAND_*__ cross-references, guardrails).

Note on tests/test_workflows.py

One assertion in test_add_from_url_download_failure_cleanup_error_preserves_original_error matched a multi-word substring against Rich-rendered panel output, which wraps mid-phrase at CI's 80-column non-TTY width and fails there. Normalized it with the same whitespace-stripping pattern already used one line above. This is the only non-assess change and is required for green CI. (A broader pass over other width-fragile tests that only fail at artificially narrow widths is left for a separate PR.)

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest — 4508 passed, 5 skipped, 0 failed at CI's 80-column width
  • Tested with a sample project (installed the extension via CLI; verified the five commands scaffold)
  • Built the wheel and confirmed assess ships in core_pack
  • Added tests/extensions/assess/test_assess_extension.py (9 tests: layout, catalog registration, bundle resolution, install, and a test_declares_no_hooks guard)

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

This change was developed with GitHub Copilot (model: Claude Opus 4.8) under human supervision. Copilot drafted the extension commands, README, tests, and packaging/registration changes; the author reviewed and validated the design, ran the test suite, and confirmed wheel packaging. Commits carry Assisted-by: trailers.

Add a role-neutral, opt-in "Idea Assessment Pipeline" extension (id:
assess) covering the discovery work that happens BEFORE spec-driven
development. It provides a five-stage funnel: intake, research, define,
shape, decide, each writing one artifact under
.specify/assessments/<slug>/. A go verdict hands off to
/speckit.specify; killing an idea is a first-class success outcome.

Registration:
- extensions/catalog.json: bundled core opt-in entry (before bug)
- pyproject.toml: force-include maps into core_pack so it ships in the
  installed wheel (verified via wheel build)

Also normalizes a Rich-wrapped substring assertion in test_workflows.py
so the suite passes at CI's 80-column non-TTY width.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 16, 2026 18:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the opt-in assess extension for evaluating ideas before specification.

Changes:

  • Adds five assessment-stage commands and documentation.
  • Registers and packages the bundled extension with an optional hook.
  • Adds extension tests and stabilizes a terminal-width-sensitive assertion.
Show a summary per file
File Description
extensions/assess/README.md Documents the assessment pipeline.
extensions/assess/extension.yml Defines commands, metadata, and hook.
extensions/assess/commands/speckit.assess.intake.md Adds idea intake workflow.
extensions/assess/commands/speckit.assess.research.md Adds evidence-gathering workflow.
extensions/assess/commands/speckit.assess.define.md Adds problem-definition workflow.
extensions/assess/commands/speckit.assess.shape.md Adds concept-shaping workflow.
extensions/assess/commands/speckit.assess.decide.md Adds assessment decision gate.
extensions/catalog.json Registers the bundled extension.
pyproject.toml Includes the extension in wheels.
tests/extensions/assess/__init__.py Initializes the test package.
tests/extensions/assess/test_assess_extension.py Tests layout, registration, and installation.
tests/test_workflows.py Normalizes wrapped CLI output assertions.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 11/12 changed files
  • Comments generated: 9
  • Review effort level: Medium

Comment thread extensions/catalog.json
Comment thread extensions/assess/extension.yml Outdated
Comment thread extensions/catalog.json Outdated
Comment thread extensions/assess/extension.yml Outdated
Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Resolve review feedback on github#3568:

- catalog.json: bump top-level updated_at to this revision (2026-07-17)
- extension.yml + catalog.json: shorten the assess description to under
  the documented 200-char manifest limit (kept aligned across both)
- extension.yml: make the before_specify hook prompt condition-neutral
  (it fires on every /speckit.specify, so it must not claim "no
  assessment found")
- intake.md: fix slug normalization to explicitly allow lowercase
  letters a-z (the old rule permitted only digits and '-', contradicting
  the offline-mode example)
- intake.md + research.md: require a sanitized source URL (strip
  userinfo and credential/signature query params) instead of persisting
  a verbatim URL that could leak secrets into project artifacts
- decide.md: remove the "trivially small" exception so a go always
  requires a shaped concept, making verdict behavior deterministic and
  consistent with the guardrails and README

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 12:52
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Addressed all nine comments in 4c8124d:

  • catalog.json freshness — bumped top-level updated_at to 2026-07-17T00:00:00Z.
  • Description length (×2) — shortened the assess description to 195 chars (under the documented 200-char limit) and kept extension.yml and catalog.json aligned.
  • before_specify hook prompt — the hook has no condition and fires on every /speckit.specify, so the prompt no longer claims "no assessment found"; it's now condition-neutral ("Assess this idea before specifying?").
  • Slug normalization — the rule now explicitly allows lowercase letters a–z alongside digits and -, matching the offline-mode example.
  • URL sanitization (intake + research) — both stages now record a sanitized URL (strip user:password@ userinfo and drop credential/signature query params such as token, sig, access_token, X-Amz-*), and templates request the redacted form so no verbatim URL with embedded secrets lands in project artifacts.
  • decide determinism — removed the "trivially small" exception; a go now always requires a shaped concept, consistent with the line-36 requirement, the no-concept guardrail, and the README.

Tests green at CI's 80-column width: tests/extensions/assess/, tests/test_extensions.py, and tests/contract/test_catalog_schema.py — 377 passed.

Disclosure: this response and commit 4c8124d were authored by GitHub Copilot (model: Claude Opus 4.8) under human supervision, on behalf of @mnriem.

Assess is a separate business process from spec-driven development, so
it should not inject itself into the /speckit.specify lifecycle. The
hook fired on every /speckit.specify invocation (it had no condition),
nagging even when an assessment already existed and the user was
deliberately proceeding.

Unlike git's before_specify (a mechanical prerequisite: create a feature
branch) or agent-context's after_* hooks (reacting to spec output),
assess is an upstream, optional, human-judgment process. The coupling
that belongs here already runs forward and by choice: a `go` verdict
from /speckit.assess.decide hands off to /speckit.specify. The backward
hook was the redundant, intrusive direction.

- extension.yml: drop the hooks block (commands-only manifest)
- README.md: replace the Hooks section with a Handoff section
- test: replace the hook assertion with test_declares_no_hooks to lock
  in the standalone-pipeline design

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 7
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/assess/commands/speckit.assess.define.md Outdated
Comment thread extensions/assess/commands/speckit.assess.shape.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Copilot AI review requested due to automatic review settings July 17, 2026 12:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 10
  • Review effort level: Medium

Comment thread tests/extensions/assess/test_assess_extension.py
Comment thread extensions/assess/commands/speckit.assess.define.md Outdated
Comment thread extensions/assess/commands/speckit.assess.shape.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/catalog.json Outdated
Comment thread extensions/assess/extension.yml Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Address the second review round on github#3568:

- Slug path traversal: intake and all four downstream commands
  (research, define, shape, decide) now normalize an explicit or
  user-supplied slug to the [a-z0-9-] alphabet (dropping '.', '/', '\\')
  and reject an empty normalized result before constructing ASSESS_DIR.
  This guarantees a slug like `../..` cannot escape .specify/assessments/.
- Metadata accuracy: the extension.yml and catalog.json descriptions no
  longer imply a "build/kill" call is handed to /speckit.specify — only a
  `go` hands off; a `kill` closes the assessment.
- Verdict determinism (decide): a `go` now explicitly requires evidence
  strength `adequate`+ (never weak/unknown), resolving the conflict with
  the thin-evidence guardrail.
- Risk polarity (decide): renamed the "Risk" criterion to "Risk posture"
  with positive polarity (strong = risks understood and mitigated) so it
  composes with the other scores that feed the verdict.
- README: aligned the go-threshold guardrail with the evidence rule and
  documented the slug-normalization safety property.

The PR description was also updated to drop the stale before_specify
hook claim (the hook was removed in the previous commit).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 13:59
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed all ten comments from this round in 9cd07fb.

Slug path traversal (intake, research, define, shape, decide) — every command that accepts an explicit or user-supplied slug now normalizes it to the [a-z0-9-] alphabet (dropping ., /, \), collapses/trims -, and rejects an empty normalized result before building ASSESS_DIR. A slug like ../.. normalizes to empty and is refused, so the assessment directory can no longer escape .specify/assessments/.

Metadata accuracy (extension.yml + catalog.json) — the descriptions no longer imply a "build/kill call" is handed to /speckit.specify. They now state: a go verdict hands off to /speckit.specify; a kill closes the assessment. (Still under the 200-char limit.)

Verdict determinism (decide) — a go now explicitly requires evidence strength adequate+ (never weak/unknown), resolving the conflict with the thin-evidence guardrail. Weak/unknown evidence deterministically yields needs-clarification.

Risk polarity (decide) — renamed the Risk criterion to Risk posture with the same positive polarity as the others (strong = key risks identified and credibly mitigated), so it composes cleanly into the verdict.

PR description — dropped the stale before_specify hook claim; the hook was removed in the prior commit (6e2d81d) because assess is a separate business process. The test line the bot flagged is the intentional test_declares_no_hooks guard, now reflected in the description.

Tests green at CI's 80-column width: tests/extensions/assess/, tests/test_extensions.py, tests/contract/test_catalog_schema.py — 377 passed.

Disclosure: this response and commit 9cd07fb were authored by GitHub Copilot (model: Claude Opus 4.8) under human supervision, on behalf of @mnriem.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 6
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.intake.md
Comment thread extensions/assess/commands/speckit.assess.research.md
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.define.md Outdated
Comment thread extensions/assess/commands/speckit.assess.shape.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
…st allowlist

Address the third review round on github#3568:

- Path safety (intake, research, define, shape, decide): slug
  normalization blocks lexical `..` but not symlinked path components.
  Each command now, before any mkdir/read/write, resolves the real path
  of .specify/assessments/<slug>/ and every artifact, refuses to follow a
  symlinked .specify / assessments / slug dir / artifact, and verifies the
  resolved path stays inside the project root. This blocks a cloned or
  crafted project from redirecting reads/writes outside the repository.
  Each stage enforces this independently since research/define/decide can
  run without intake.
- research URL policy: replaced the open-ended "and comparable well-known
  hosts" no-prompt branch with intake's exact enumerated allowlist, so an
  agent cannot classify an attacker-controlled host as "comparable" and
  fetch it without confirmation.
- README: guardrail now documents symlink/realpath containment.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 14:30
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed all six comments from this round in c032a2e.

Symlink traversal (intake, research, define, shape, decide) — you're right that normalizing the slug only blocks lexical ..; a symlinked .specify, assessments, <slug> directory, or artifact could still redirect reads/writes outside the project. Each command now carries an explicit Path safety prerequisite: before any mkdir/read/write it resolves the real (symlink-resolved) path of .specify/assessments/<slug>/ and every artifact, refuses to follow a symlinked path component or target, and verifies the resolved path stays inside the project root. Each stage enforces this independently, since research/define/decide can run without intake.

Open-ended host heuristic (research) — replaced the "and comparable well-known hosts" no-prompt branch with intake's exact enumerated allowlist (github.com, gist.github.com, gitlab.com, bitbucket.org, *.atlassian.net, linear.app, notion.so, *.notion.site, docs.google.com, stackoverflow.com, *.stackexchange.com). Any host off that list is unrecognized and requires confirmation; the agent may no longer classify a host as "comparable" and fetch it silently.

The README guardrail now documents the symlink/realpath containment property too.

Tests green at CI's 80-column width: tests/extensions/assess/, tests/test_extensions.py, tests/contract/test_catalog_schema.py — 377 passed.

Disclosure: this response and commit c032a2e were authored by GitHub Copilot (model: Claude Opus 4.8) under human supervision, on behalf of @mnriem.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
…-slug collision

Address the fourth review round on github#3568 (intake):

- Secret leak in the captured idea: quoting the original "verbatim"
  contradicted the URL sanitization rule when the idea itself contained a
  credential-bearing URL. Capture now redacts secrets (sanitize URLs;
  strip tokens, passwords, keys, cookies) inside the quoted text as well
  as the Source field, and the section heading is "Idea (as captured)"
  rather than "verbatim".
- Explicit-slug collision: in automated mode an existing intake.md caused
  a silent switch to a new slug, contradicting the no-suffix guarantee for
  user-provided slugs. Now: user-provided slug collision -> stop and
  report; only a self-generated slug (already disambiguated at resolution)
  is re-slugged.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 14:46
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed both comments from this round in 05968e4 (both in intake.md).

Secret leak in the captured idea — you're right that quoting the original "verbatim" contradicted the URL-sanitization rule when the idea itself contains a credential-bearing URL. Capture step 1 and the template now redact secrets inside the quoted text too (sanitize URLs; strip tokens, passwords, keys, cookies), matching the Source field. Renamed the section from "Idea (verbatim)" to "Idea (as captured)" so the heading no longer implies literal preservation of secrets.

Explicit-slug collision — in automated mode an existing intake.md previously triggered a silent switch to a new slug, contradicting line 30 and the README guarantee that user-provided slugs never gain suffixes. Now: a user-provided slug collision stops and reports; only a self-generated slug (already disambiguated during resolution) is re-slugged.

Tests green at CI's 80-column width: tests/extensions/assess/ and tests/contract/test_catalog_schema.py — 21 passed.

Disclosure: this response and commit 05968e4 were authored by GitHub Copilot (model: Claude Opus 4.8) under human supervision, on behalf of @mnriem.

…policy

Address the remaining open comment from review 4722852090 on
github#3568 (the other six comments in that round were already
resolved by the slug-validation and host-allowlist fixes in 9cd07fb and
c032a2e).

The URL Trust Policy refused only textual IPv4 loopback/RFC1918/metadata
hosts, so an approved hostname resolving to an internal IPv6 or
IPv4-mapped address could still reach internal services. The refuse-
outright list now covers IPv6 link-local (fe80::/10), unique-local
(fc00::/7), IPv4-mapped forms, and the IPv6 metadata address, and adds a
resolution-time check: even an allowlisted or user-confirmed host is
refused when it resolves to any non-public address, defeating DNS
rebinding. Mirrored the summary in research's inherited-policy note.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

While reconciling review threads I found an earlier review round (4722852090, 7 comments) that hadn't been explicitly addressed. Six of its seven comments were already resolved by later commits:

  • Empty/separator-only slug (intake) → fixed in 9cd07fb (reject empty normalized slug).
  • Open-ended "comparable well-known hosts" (research) → fixed in c032a2e (pinned to intake's exact allowlist).
  • Missing slug normalization/validation in define, shape, decide → fixed in 9cd07fb.
  • Undefined normalization/reject in research → fixed in 9cd07fb / c032a2e.

The one remaining comment is now addressed in b3faa63:

SSRF / IPv6 gap in the URL Trust Policy — the refuse-outright list only covered textual IPv4 loopback/RFC1918/metadata hosts. It now also refuses IPv6 link-local (fe80::/10), unique-local (fc00::/7), IPv4-mapped forms, and the IPv6 metadata address, and adds a resolution-time check: even an allowlisted or user-confirmed host is refused if it resolves to a non-public address, which defeats DNS rebinding. Mirrored the summary in research's inherited-policy note so that command is self-contained.

Tests green at CI's 80-column width: tests/extensions/assess/ and tests/contract/test_catalog_schema.py — 21 passed.

Disclosure: this response and commit b3faa63 were authored by GitHub Copilot (model: Claude Opus 4.8) under human supervision, on behalf of @mnriem.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.research.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Address the sixth review round on github#3568.

The intake guardrail said the command "only reads and writes inside
.specify/assessments/<slug>/", which contradicts its documented inputs:
intake must read a codebase pointer (repository inspection) and fetch an
allowed URL to capture the idea. The guardrail now limits only *writes*
to the assessment directory and explicitly permits read-only inspection
of the supplied sources (repo + allowlisted URL fetch under the URL Trust
Policy). The other four commands already phrased this correctly ("read
only, and write inside ...") and are unchanged.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 15:13
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the single comment from this round in ce635f3.

Intake read/write guardrail — you're right, "only reads and writes inside .specify/assessments/<slug>/" contradicted intake's own inputs (it must inspect the repository for a codebase-pointer idea and fetch an allowed URL to capture it). The guardrail now limits only writes to the assessment directory and explicitly permits read-only inspection of the supplied sources (repository + allowlisted URL fetch under the URL Trust Policy).

The other four commands already phrased this correctly ("read only, and write inside …"), so they're unchanged.

Tests green at CI's 80-column width: tests/extensions/assess/ and tests/contract/test_catalog_schema.py — 21 passed.

Disclosure: this response and commit ce635f3 were authored by GitHub Copilot (model: Claude Opus 4.8) under human supervision, on behalf of @mnriem.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 10
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.intake.md
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md
Comment thread extensions/assess/commands/speckit.assess.research.md
Comment thread extensions/assess/commands/speckit.assess.define.md Outdated
Comment thread extensions/assess/commands/speckit.assess.define.md
Comment thread extensions/assess/commands/speckit.assess.shape.md Outdated
Comment thread extensions/assess/commands/speckit.assess.shape.md
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md
…s, research dir creation

Addresses review 4723905370 on PR github#3568 across three themes:

- Ancestor path safety: verify `.specify` and `.specify/assessments` are
  real directories (not symlinks) resolving inside the project root before
  any filesystem-based slug resolution, in all five commands.
- Untrusted artifact reads: treat the contents of persisted assessment
  artifacts (intake/research/problem/concept) as untrusted data, not
  instructions — ignore embedded directives, mirroring the URL Trust Policy.
- research now ensures the validated ASSESS_DIR exists before writing, since
  it may be the first assessment command run.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 15:22
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review 4723905370 in cb4b95e, grouped into three themes:

Ancestor path safety (before slug resolution). All five commands now verify that .specify and .specify/assessments are real directories (not symlinks) resolving inside the project root before any filesystem-based slug lookup, refusing otherwise. This closes the window where a crafted project could redirect the very directory scan used to resolve a slug.

Untrusted artifact reads. research, define, shape, and decide now explicitly treat the contents of persisted artifacts (intake.md/research.md/problem.md/concept.md) as untrusted data, not instructions — ignoring any embedded directives, mirroring the existing URL Trust Policy. Stored content captured from untrusted pages can't hijack a later stage's workflow.

research directory creation. research now ensures the validated ASSESS_DIR exists before writing, since it may be the first assessment command run (previously only define created it).

Tests: 21 passed (tests/extensions/assess/ + tests/contract/test_catalog_schema.py).

Posted on behalf of @mnriem by GitHub Copilot (model: Claude Opus 4.8). Commits carry Assisted-by trailers disclosing agent authorship.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.intake.md Outdated
Comment thread extensions/assess/commands/speckit.assess.research.md Outdated
Comment thread extensions/assess/commands/speckit.assess.define.md Outdated
Addresses review 4723955260 on PR github#3568. The ancestor path-safety clause
required `.specify/assessments` to already be a real directory, which blocked
the first-run commands (intake, research, define) from ever reaching the step
that creates it. Reword the clause in all five commands so a not-yet-created
directory is permitted, while still refusing when `.specify` or
`.specify/assessments` exists as a symlink or escapes the project root.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 15:29
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review 4723955260 in b2f609e.

Good catch — the ancestor path-safety clause I added last round required .specify/assessments to already be a real directory, which contradicted the first-run flows: intake, research, and define are all allowed to run before the directory exists, so refusing on absence blocked them from reaching the step that creates it.

Reworded the clause in all five commands so that a not-yet-created directory is permitted (it will be created safely later), while still refusing when .specify or .specify/assessments exists as a symlink or escapes the project root. The symlink/containment protection is preserved; only the false rejection on absence is removed.

Tests: 21 passed (tests/extensions/assess/ + tests/contract/test_catalog_schema.py).

Posted on behalf of @mnriem by GitHub Copilot (model: Claude Opus 4.8). Commits carry Assisted-by trailers disclosing agent authorship.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread extensions/assess/README.md Outdated
Addresses review 4724027270 on PR github#3568. The overview flowchart routed every
needs-clarification verdict back to research, but decide.md's Revisit stage can
send an idea back to intake, research, define, or shape. Reroute the arrow as a
generic loop back to the earlier stages so the diagram no longer misstates the
pipeline.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 15:37
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review 4724027270 in a3e2ac1.

The overview flowchart routed every needs-clarification verdict back to research, but decide.md's Revisit stage can send an idea back to whichever earlier stage is under-baked — intake, research, define, or shape. Rerouted the arrow as a generic dashed loop back to the earlier stages (needs-clarification: revisit the named earlier stage) so the diagram matches the actual decision workflow instead of implying research is the only re-entry point.

Docs-only change; assess tests still pass (9 passed).

Posted on behalf of @mnriem by GitHub Copilot (model: Claude Opus 4.8). Commits carry Assisted-by trailers disclosing agent authorship.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Comment thread extensions/assess/commands/speckit.assess.decide.md Outdated
Addresses review 4724080304 on PR github#3568:

- decide.md frontmatter description hard-coded `/speckit.specify`. Frontmatter
  is parsed before command-reference resolution, so it now uses agent-neutral
  wording ("hand survivors off into Spec-Driven Development") instead of a
  dot-style literal that would be wrong for non-dot integrations.
- The `## If go — Handoff to …` heading inside the decision.md output template
  hard-coded `/speckit.specify`, which would be written verbatim into
  decision.md. It now uses the `__SPECKIT_COMMAND_SPECIFY__` placeholder, like
  the rest of the command, so the active integration's invocation style is
  rendered.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a
Copilot AI review requested due to automatic review settings July 17, 2026 15:48
@mnriem

mnriem commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review 4724080304 in c59c457. Both comments concerned hard-coded dot-style /speckit.specify in decide.md:

Frontmatter description (line 2). Since extension frontmatter is parsed before command-reference resolution, a placeholder wouldn't render there. Switched to agent-neutral wording — "hand survivors off into Spec-Driven Development" — so generated help/skill metadata is correct regardless of the integration's command style.

Handoff heading in the decision.md template (line 74). This literal was inside the fenced block that gets written verbatim into decision.md, so non-dot integrations would have recorded an invalid /speckit.specify handoff. It now uses the __SPECKIT_COMMAND_SPECIFY__ placeholder, matching the rest of this command, so the active integration's invocation style is rendered.

No dot-style literals remain in the command files. Tests: 21 passed.

Posted on behalf of @mnriem by GitHub Copilot (model: Claude Opus 4.8). Commits carry Assisted-by trailers disclosing agent authorship.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 11/12 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

@mnriem
mnriem merged commit 208d386 into github:main Jul 17, 2026
12 checks passed
@mnriem
mnriem deleted the mnriem-feat-assess-extension branch July 17, 2026 16:11
satwareAG-ironMike added a commit to satwareAG/spec-kit that referenced this pull request Jul 19, 2026
* chore: release 0.12.17, begin 0.12.18.dev0 development (github#3560)

* chore: bump version to 0.12.17

* chore: begin 0.12.18.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update DocGuard — CDD Enforcement to v0.33.0 (github#3559)

Update docguard extension submitted by @raccioly:
- extensions/catalog.community.json (version, download_url, description, updated_at)
- docs/community/extensions.md community extensions table

Closes github#3556

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* [extension] Add Dotdog extension to community catalog (github#3558)

* Add Dotdog extension to community catalog

Add dotdog extension submitted by @logohere to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes github#3555

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(catalog): alphabetize dotdog entry and correct tool requirement

Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* docs: refresh landing page ecosystem stats (github#3561)

* docs: refresh landing page ecosystem stats

Update stale numbers in docs/index.md to match current catalogs on
upstream/main and live GitHub data: extensions 105->138, presets
22->25, integrations 30+->35, contributors 200+->240+, friends 4->6,
GitHub stars 106K+->121K+, extension authors 60+->70+, and the
last-updated date.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e5f34221-4e6c-42e8-9fa3-5cfbc26104d1

* docs: align community extension stats on overview page

Update docs/community/overview.md from "Over 90 ... 50+ authors" to
"Over 130 ... 70+ authors" so it matches the refreshed landing-page
numbers in docs/index.md (137 community extensions, 77 unique authors).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 134166d9-e599-44fa-a88d-daf84ab6aca6

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs: document extensions.yml hook configuration (github#3563)

* docs: document extesnion.yml hook confriguration

* docs: address hook confriguration review feedback

* clarify auto execute hooks behavior

* docs: clarify hook priority and condition behavior

* docs: reframe SDD positioning, modernize install, and de-duplicate walkthroughs (github#3565)

* docs: reframe SDD positioning, modernize install, and de-duplicate walkthroughs

Reframe the landing page so Spec Kit reads as a toolkit for Spec-Driven
Development *or your own process* with any AI coding agent, and correct
stale claims: context files and git are now opt-in extensions, and the
install path uses PyPI (specify-cli). Generalize the landing cards to
cover bundles and catalog hosting across all primitives.

Restructure the Quick Start into a lean, guided Taskify walkthrough with
one command per step (install as a prerequisite, Steps 1-9 aligned with
the Full path), and extract the deep per-command detail into two new
reference pages: reference/agentic-sdd.md (the /speckit.* SDD process)
and reference/agentic-bugfix.md (the bug extension). Retitle the
reference overview to "Reference" and group these agentic processes in
their own section, distinct from CLI-managed primitives.

Remove the duplicated "Detailed Process" walkthrough from README.md
(and its TOC entry), repointing readers to the docs-site Quick Start
while keeping the concise "Get Started" section as the front door.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89

* docs: address review feedback on accuracy and scope

- quickstart: correct the git/feature note — resolution reads
  .specify/feature.json / SPECIFY_FEATURE, not the checked-out branch,
  so switching branches alone does not switch the active feature.
- quickstart + agentic-sdd: add an invocation-style note ($speckit-* for
  Codex/ZCode, /skill:speckit-* for Kimi) so the agent-neutral commands
  are executable everywhere.
- agentic-sdd: fix the tasks phase structure to match the generator
  (Setup, Foundational, one phase per user story, final Polish; tests
  optional within user-story phases).
- index: soften the catalog claim (catalogs curate discovery, not an
  install allow-list) and relabel the "CLI reference" link to "Reference"
  to match the retitled, broader page.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89

* docs: correct feature-resolution override and add bug-command invocation note

- quickstart: the previous fix named the wrong override. The active
  feature *directory* resolves from SPECIFY_FEATURE_DIRECTORY then
  .specify/feature.json; SPECIFY_FEATURE only supplies the identifier
  after a directory is resolved. Rewrite the note to point users at
  .specify/feature.json / SPECIFY_FEATURE_DIRECTORY, and clarify the git
  extension's branches don't by themselves change the active feature.
- agentic-bugfix: add the same invocation-style caveat as the SDD
  reference ($speckit-bug-* for Codex/ZCode, /skill:speckit-bug-* for
  Kimi).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89

* docs: tighten bug-command contracts and drop numbered-phase examples

- agentic-bugfix: don't overstate overwrite protection — an interactive
  run can overwrite an existing assessment after confirmation; only
  automated mode refuses and picks a new slug. Correct the verify verdict
  to the schema's verified/partial/failed (not-run is a per-check status);
  an unexercised reproduction downgrades the result to partial.
- agentic-sdd: the implement examples labeled scoping "Phase 1/2", but
  the tasks contract reserves Phase 1 for Setup and Phase 2 for
  Foundational (user stories start at Phase 3). Scope by phase name and
  user-story content instead to avoid mis-scoping execution.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d9232f8-ece4-4aa6-a9bd-ff8d74ca1c89

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs: weave harness/SDLC framing into landing page (github#3567)

Reframe the docs landing hero and "Make it your own" pillar to inject
the "harness" and "SDLC" framing while keeping SDD front and center:

- Hero: describe Spec Kit as an extensible, intent-driven harness that
  pushes any coding agent beyond code, across the SDLC or any business
  process; tagline now contrasts step-by-step vs automated-workflow runs.
- "Make it your own": explain the process lives in swappable building
  blocks (not locked to SDD or even software) and add a real non-software
  preset (Fiction Book Writing) to back the broadened scope.
- Community blurb: drop "development" so "entirely new processes" matches
  the wider positioning.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Copilot-Session: 1cf71797-ac0d-4a5e-8266-784906933b54

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/setup-node from 6.4.0 to 7.0.0 (github#3570)

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.4.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@48b55a0...8207627)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/stale from 10.3.0 to 10.4.0 (github#3572)

Bumps [actions/stale](https://github.com/actions/stale) from 10.3.0 to 10.4.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](actions/stale@eb5cf3a...1e223db)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: 10.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/setup-dotnet from 5.4.0 to 6.0.0 (github#3574)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.4.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](actions/setup-dotnet@26b0ec1...a98b568)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: release 0.12.18, begin 0.12.19.dev0 development (github#3583)

* chore: bump version to 0.12.18

* chore: begin 0.12.19.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* docs: align README hero tagline and subtitle with docs/index.md (github#3581)

Match the README hero tagline to the docs landing hero and rewrite the
subtitle to reflect the four-pillar positioning (ready-to-use spec-driven
process or bring your own, extensible, community-driven, org-ready) rather
than framing everything around SDD.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Copilot-Session: da32794c-5044-406c-9338-12b3ffab49f4

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump github/codeql-action/init from 4.36.2 to 4.37.1 (github#3571)

* chore(deps): bump github/codeql-action/init from 4.36.2 to 4.37.1

Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.2 to 4.37.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...7188fc3)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump github/codeql-action/analyze to 4.37.1

Bump the analyze step to match the init step (both now 7188fc3 / v4.37.1).
Dependabot bumped only init, leaving analyze on 4.36.2, which caused CodeQL
to fail with "Loaded a configuration file for version '4.37.1', but running
version '4.36.2'". Both steps must reference the same release.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mnriem <mnriem@users.noreply.github.com>

* fix(presets): raise PresetValidationError, not raw ValueError, on malformed catalog URL (github#3576)

`PresetCatalog._validate_catalog_url` called `urlparse(url).hostname` without
guarding it. For a malformed authority such as an unterminated IPv6 bracket
(`https://[::1`), `urlparse(...).hostname` raises `ValueError: Invalid IPv6 URL`,
which escapes the method. Its docstring promises `PresetValidationError`, and its
callers (`preset catalog add`, `preset catalog list` reading the
`SPECKIT_PRESET_CATALOG_URL` env var / `.specify/preset-catalogs.yml`) only catch
`PresetValidationError` -- so a malformed URL crashes the CLI with a traceback
instead of a clean error message.

The shared `CatalogStackBase` (github#3435), `workflows` (github#3484), `bundler` (github#3433) and
`IntegrationCatalog` copies already wrap this in `try/except ValueError`; the
preset validator was the remaining un-updated twin. Mirror the shared
implementation: wrap `urlparse` + `.hostname`, re-raise as
`PresetValidationError("Catalog URL is malformed: ...")`, and read the local
`hostname` in the host check.

Add a regression test mirroring `IntegrationCatalog`'s
`test_malformed_url_rejected_cleanly`; it is red before the fix (raw `ValueError`)
and green after.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: update extension guide PyPI upgrade guidance (github#3578)

Co-authored-by: root <kinsonnee@gmail.com>

* Update Autonomous Run Governance preset to v0.2.2 (github#3584)

Update autonomous-run-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, provides, tags, updated_at)
- docs/community/presets.md community presets table

Closes github#3569

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add OKF Knowledge Bundle Generator extension to community catalog (github#3585)

Add okf extension submitted by @alexcpn to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes github#3580

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(bundle): surface a clean BundlerError on a malformed bundle download URL (github#3586)

`_download_manifest` and its `_require_https` helper parsed the catalog
entry's `download_url` with an unguarded `urlparse(url)`. A malformed
authority — e.g. an unclosed IPv6 bracket like `https://[::1` — makes
`urlparse` (or `.hostname` on older Pythons) raise a raw `ValueError`. The
three `bundle` CLI commands (`info`, `install`, `update`) only catch
`BundlerError`, so that `ValueError` escaped as an uncaught traceback.

Wrap both parse sites in the same `try/except ValueError -> BundlerError`
guard already used by the sibling `_validate_remote_url` (and established by
the merged catalog-URL fix github#3576), so a bad `download_url` reports a clean,
actionable error in every mode.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(extensions): add assess idea assessment pipeline extension (github#3568)

* feat(extensions): add assess idea assessment pipeline extension

Add a role-neutral, opt-in "Idea Assessment Pipeline" extension (id:
assess) covering the discovery work that happens BEFORE spec-driven
development. It provides a five-stage funnel: intake, research, define,
shape, decide, each writing one artifact under
.specify/assessments/<slug>/. A go verdict hands off to
/speckit.specify; killing an idea is a first-class success outcome.

Registration:
- extensions/catalog.json: bundled core opt-in entry (before bug)
- pyproject.toml: force-include maps into core_pack so it ships in the
  installed wheel (verified via wheel build)

Also normalizes a Rich-wrapped substring assertion in test_workflows.py
so the suite passes at CI's 80-column non-TTY width.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): address PR review on assess extension

Resolve review feedback on github#3568:

- catalog.json: bump top-level updated_at to this revision (2026-07-17)
- extension.yml + catalog.json: shorten the assess description to under
  the documented 200-char manifest limit (kept aligned across both)
- extension.yml: make the before_specify hook prompt condition-neutral
  (it fires on every /speckit.specify, so it must not claim "no
  assessment found")
- intake.md: fix slug normalization to explicitly allow lowercase
  letters a-z (the old rule permitted only digits and '-', contradicting
  the offline-mode example)
- intake.md + research.md: require a sanitized source URL (strip
  userinfo and credential/signature query params) instead of persisting
  a verbatim URL that could leak secrets into project artifacts
- decide.md: remove the "trivially small" exception so a go always
  requires a shaped concept, making verdict behavior deterministic and
  consistent with the guardrails and README

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* refactor(extensions): remove before_specify hook from assess

Assess is a separate business process from spec-driven development, so
it should not inject itself into the /speckit.specify lifecycle. The
hook fired on every /speckit.specify invocation (it had no condition),
nagging even when an assessment already existed and the user was
deliberately proceeding.

Unlike git's before_specify (a mechanical prerequisite: create a feature
branch) or agent-context's after_* hooks (reacting to spec output),
assess is an upstream, optional, human-judgment process. The coupling
that belongs here already runs forward and by choice: a `go` verdict
from /speckit.assess.decide hands off to /speckit.specify. The backward
hook was the redundant, intrusive direction.

- extension.yml: drop the hooks block (commands-only manifest)
- README.md: replace the Hooks section with a Handoff section
- test: replace the hook assertion with test_declares_no_hooks to lock
  in the standalone-pipeline design

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): harden assess slug handling and clarify verdict logic

Address the second review round on github#3568:

- Slug path traversal: intake and all four downstream commands
  (research, define, shape, decide) now normalize an explicit or
  user-supplied slug to the [a-z0-9-] alphabet (dropping '.', '/', '\\')
  and reject an empty normalized result before constructing ASSESS_DIR.
  This guarantees a slug like `../..` cannot escape .specify/assessments/.
- Metadata accuracy: the extension.yml and catalog.json descriptions no
  longer imply a "build/kill" call is handed to /speckit.specify — only a
  `go` hands off; a `kill` closes the assessment.
- Verdict determinism (decide): a `go` now explicitly requires evidence
  strength `adequate`+ (never weak/unknown), resolving the conflict with
  the thin-evidence guardrail.
- Risk polarity (decide): renamed the "Risk" criterion to "Risk posture"
  with positive polarity (strong = risks understood and mitigated) so it
  composes with the other scores that feed the verdict.
- README: aligned the go-threshold guardrail with the evidence rule and
  documented the slug-normalization safety property.

The PR description was also updated to drop the stale before_specify
hook claim (the hook was removed in the previous commit).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): add symlink/realpath containment and pin research host allowlist

Address the third review round on github#3568:

- Path safety (intake, research, define, shape, decide): slug
  normalization blocks lexical `..` but not symlinked path components.
  Each command now, before any mkdir/read/write, resolves the real path
  of .specify/assessments/<slug>/ and every artifact, refuses to follow a
  symlinked .specify / assessments / slug dir / artifact, and verifies the
  resolved path stays inside the project root. This blocks a cloned or
  crafted project from redirecting reads/writes outside the repository.
  Each stage enforces this independently since research/define/decide can
  run without intake.
- research URL policy: replaced the open-ended "and comparable well-known
  hosts" no-prompt branch with intake's exact enumerated allowlist, so an
  agent cannot classify an attacker-controlled host as "comparable" and
  fetch it without confirmation.
- README: guardrail now documents symlink/realpath containment.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): redact secrets in captured idea and stop on explicit-slug collision

Address the fourth review round on github#3568 (intake):

- Secret leak in the captured idea: quoting the original "verbatim"
  contradicted the URL sanitization rule when the idea itself contained a
  credential-bearing URL. Capture now redacts secrets (sanitize URLs;
  strip tokens, passwords, keys, cookies) inside the quoted text as well
  as the Source field, and the section heading is "Idea (as captured)"
  rather than "verbatim".
- Explicit-slug collision: in automated mode an existing intake.md caused
  a silent switch to a new slug, contradicting the no-suffix guarantee for
  user-provided slugs. Now: user-provided slug collision -> stop and
  report; only a self-generated slug (already disambiguated at resolution)
  is re-slugged.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): reject IPv6 private ranges and DNS-rebinding in URL policy

Address the remaining open comment from review 4722852090 on
github#3568 (the other six comments in that round were already
resolved by the slug-validation and host-allowlist fixes in 9cd07fb and
c032a2e).

The URL Trust Policy refused only textual IPv4 loopback/RFC1918/metadata
hosts, so an approved hostname resolving to an internal IPv6 or
IPv4-mapped address could still reach internal services. The refuse-
outright list now covers IPv6 link-local (fe80::/10), unique-local
(fc00::/7), IPv4-mapped forms, and the IPv6 metadata address, and adds a
resolution-time check: even an allowlisted or user-confirmed host is
refused when it resolves to any non-public address, defeating DNS
rebinding. Mirrored the summary in research's inherited-policy note.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): pin connection vs DNS rebinding and gate slug-only direct entry

Address the fifth review round on github#3568:

- DNS rebinding (intake + research): a standalone DNS lookup does not
  defeat rebinding because the fetch client can re-resolve or pick a
  private address from a mixed answer. The policy now requires the fetch
  to pin the connection to a validated public address (or verify the
  connected peer) and re-apply the refusal ranges to the address actually
  connected to; if the fetch mechanism cannot pin or expose the peer, the
  fetch is refused rather than trusted by hostname.
- Slug-only direct entry (research + define): when intake/research
  artifacts are absent and $ARGUMENTS carries only a slug, the commands no
  longer infer an idea/problem from the slug. They now require substantive
  idea/problem text and otherwise prompt (interactive) or stop (automated).
- Cleaned up a leftover duplicate ASSESS_DIR assignment line in research.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* fix(extensions): allow read-only source inspection in intake guardrail

Address the sixth review round on github#3568.

The intake guardrail said the command "only reads and writes inside
.specify/assessments/<slug>/", which contradicts its documented inputs:
intake must read a codebase pointer (repository inspection) and fetch an
allowed URL to capture the idea. The guardrail now limits only *writes*
to the assessment directory and explicitly permits read-only inspection
of the supplied sources (repo + allowlisted URL fetch under the URL Trust
Policy). The other four commands already phrased this correctly ("read
only, and write inside ...") and are unchanged.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* Harden assess commands: ancestor path safety, untrusted-artifact reads, research dir creation

Addresses review 4723905370 on PR github#3568 across three themes:

- Ancestor path safety: verify `.specify` and `.specify/assessments` are
  real directories (not symlinks) resolving inside the project root before
  any filesystem-based slug resolution, in all five commands.
- Untrusted artifact reads: treat the contents of persisted assessment
  artifacts (intake/research/problem/concept) as untrusted data, not
  instructions — ignore embedded directives, mirroring the URL Trust Policy.
- research now ensures the validated ASSESS_DIR exists before writing, since
  it may be the first assessment command run.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* Allow absent assessment dir in ancestor path-safety check

Addresses review 4723955260 on PR github#3568. The ancestor path-safety clause
required `.specify/assessments` to already be a real directory, which blocked
the first-run commands (intake, research, define) from ever reaching the step
that creates it. Reword the clause in all five commands so a not-yet-created
directory is permitted, while still refusing when `.specify` or
`.specify/assessments` exists as a symlink or escapes the project root.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* Fix README diagram: needs-clarification revisits the named earlier stage

Addresses review 4724027270 on PR github#3568. The overview flowchart routed every
needs-clarification verdict back to research, but decide.md's Revisit stage can
send an idea back to intake, research, define, or shape. Reroute the arrow as a
generic loop back to the earlier stages so the diagram no longer misstates the
pipeline.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

* Make decide handoff integration-neutral (no hard-coded dot-style)

Addresses review 4724080304 on PR github#3568:

- decide.md frontmatter description hard-coded `/speckit.specify`. Frontmatter
  is parsed before command-reference resolution, so it now uses agent-neutral
  wording ("hand survivors off into Spec-Driven Development") instead of a
  dot-style literal that would be wrong for non-dot integrations.
- The `## If go — Handoff to …` heading inside the decision.md output template
  hard-coded `/speckit.specify`, which would be written verbatim into
  decision.md. It now uses the `__SPECKIT_COMMAND_SPECIFY__` placeholder, like
  the rest of the command, so the active integration's invocation style is
  rendered.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f25cf9d-b7eb-4b2b-b811-3e91d8db8f6a

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(auth): Azure DevOps az-CLI token acquisition returns None on undecodable output (github#3527)

_acquire_via_az_cli runs 'az account get-access-token' with text=True, so
subprocess.run decodes stdout with the locale encoding and raises
UnicodeDecodeError (a ValueError sibling, NOT a JSONDecodeError) when the output
can't be decoded. That escaped the except (OSError, TimeoutExpired,
JSONDecodeError, KeyError) tuple and crashed a helper whose contract is to
return str | None. Add UnicodeDecodeError to the tuple.

Test patches subprocess.run to raise UnicodeDecodeError and asserts resolve_token
returns None (fails before: the error propagated).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: bump version to 0.13.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Ehtasham Yasin <ehtasham.yasin.dev@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mnriem <mnriem@users.noreply.github.com>
Co-authored-by: Andrew Chen <48723787+chuenchen309@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: WOLIKIMCHENG <35391914+WOLIKIMCHENG@users.noreply.github.com>
Co-authored-by: root <kinsonnee@gmail.com>
Co-authored-by: Ali jawwad <33836051+jawwad-ali@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants